/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/

#include "kimagedx.h"
#include "kgraphicsdx.h"
#include "kdata.h"
#include "kerror.h"
#include "kimagefile.h"

void kImageDX::resetDevice(bool mode) {
	if (_pool == D3DPOOL_MANAGED)
		return;

	if (!mode) {
		if (_texture) {
			_texture->Release();
			_texture = 0;
		}
		if (_surface) {
			_surface->Release();
			_surface = 0;
		}
	} else {
		_graphics->createImageDX(_size, _options, &_texture, &_surface);
	}
}
kImageDX::kImageDX(kGraphicsDX* graphics, IDirect3DTexture9* texture) : kImage(graphics->getEngine()) {
	_graphics = graphics;
	_texture = texture;
	_pool = D3DPOOL_MANAGED;

	_graphics->_images.insert(this);

	if (_texture) {
		_texture->AddRef();

		_surface = 0;
		_texture->GetSurfaceLevel(0, &_surface);

		if (_surface) {
			D3DSURFACE_DESC desc;
			_surface->GetDesc(&desc);

			_size.x = desc.Width;
			_size.y = desc.Height;

			_pool = desc.Pool;

			_options |= keImage_Drawable;

			if (desc.Usage & D3DUSAGE_RENDERTARGET)
				_options |= keImage_RenderTarget;

			if (_pool == D3DPOOL_MANAGED || desc.Usage & D3DUSAGE_DYNAMIC)
				_options |= keImage_Lockable;

			if (desc.Format != D3DFMT_A8R8G8B8 && desc.Format != D3DFMT_A1R5G5B5 &&
				desc.Format != D3DFMT_A4R4G4B4 && desc.Format != D3DFMT_A8R3G3B2 &&
				desc.Format != D3DFMT_A2B10G10R10 && desc.Format != D3DFMT_A8B8G8R8 &&
				desc.Format != D3DFMT_A2R10G10B10 && desc.Format != D3DFMT_A16B16G16R16)
				_options |= keImage_Opaque;

		} else {
			_texture = 0;
		}
	}
}
kImageDX::kImageDX(kGraphicsDX* graphics, IDirect3DSurface9* surface) : kImage(graphics->getEngine()) {
	_graphics = graphics;
	_surface = surface;
	_texture = 0;
	_pool = D3DPOOL_MANAGED;

	_graphics->_images.insert(this);

	if (_surface) {
		_surface->AddRef();

		D3DSURFACE_DESC desc;
		_surface->GetDesc(&desc);

		_size.x = desc.Width;
		_size.y = desc.Height;
		_pool = desc.Pool;

		if (desc.Usage & D3DUSAGE_RENDERTARGET)
			_options |= keImage_RenderTarget;

		if (_pool == D3DPOOL_MANAGED || desc.Usage & D3DUSAGE_DYNAMIC)
			_options |= keImage_Lockable;

		if (desc.Format != D3DFMT_A8R8G8B8 && desc.Format != D3DFMT_A1R5G5B5 &&
			desc.Format != D3DFMT_A4R4G4B4 && desc.Format != D3DFMT_A8R3G3B2 &&
			desc.Format != D3DFMT_A2B10G10R10 && desc.Format != D3DFMT_A8B8G8R8 &&
			desc.Format != D3DFMT_A2R10G10B10 && desc.Format != D3DFMT_A16B16G16R16)
			_options |= keImage_Opaque;
	}
}
kImageDX::~kImageDX() {
	_graphics->_images.erase(this);

	if (_surface) {
		_surface->Release();
		_surface = 0;
	}
	if (_texture) {
		_texture->Release();
		_texture = 0;
	}
}
void kImageDX::save(kStream* stream, keImageFormat format) const {
	D3DLOCKED_RECT rc;
	D3DSURFACE_DESC desc;
	
	if (_texture) {
		_texture->LockRect(0, &rc, 0, D3DLOCK_READONLY);

		_texture->GetLevelDesc(0, &desc);
	} else {
		_surface->LockRect(&rc, 0, D3DLOCK_READONLY);

		_surface->GetDesc(&desc);
	}

	kImageHeader header;
	header.format = format;
	header.width = _size.x;
	header.height = _size.y;
	header.depth = (getOptions() & keImage_Opaque) ? 3 : 4;
	uint32_t memDepth = 0;

	switch (desc.Format) {
    case D3DFMT_R8G8B8:
		memDepth = 3;
		break;
	case D3DFMT_A8R8G8B8:
	case D3DFMT_X8R8G8B8:
		memDepth = 4;
		break;
	default:
		throw kError(_T("Unexpected image format!"));
	}

	kImageFile::save(stream, &header, [&](uint32_t y, uint8_t* dest) {
		uint8_t* src = (uint8_t*)rc.pBits + y*rc.Pitch;
		for (uint32_t x = 0; x < header.width; x++) {
			dest[x*header.depth] = src[x*memDepth];
			dest[x*header.depth+1] = src[x*memDepth+1];
			dest[x*header.depth+2] = src[x*memDepth+2];
			if (header.depth == 4)
				dest[x*header.depth+3] = src[x*memDepth+3];
		}
	});

	if (_texture) {
		_texture->UnlockRect(0);
	} else {
		_surface->UnlockRect();
	}
}
void kImageDX::doProcess(kRect* region, bool readonly, std::function<void (const kVec2i&, kColor&)> lambda) const {
	D3DLOCKED_RECT rc;
	D3DSURFACE_DESC desc;
	
	RECT rect;
	if (region) {
		rect.left = region->left;
		rect.top = region->top;
		rect.right = region->left + region->width;
		rect.bottom = region->top + region->height;
	} else {
		rect.left = 0;
		rect.top = 0;
		rect.right = _size.x;
		rect.bottom = _size.y;
	}

	if (_texture) {
		_texture->LockRect(0, &rc, &rect, readonly ? D3DLOCK_READONLY : 0);

		_texture->GetLevelDesc(0, &desc);
	} else {
		_surface->LockRect(&rc, &rect, readonly ? D3DLOCK_READONLY : 0);

		_surface->GetDesc(&desc);
	}

	size_t depth;
	switch (desc.Format) {
    case D3DFMT_R8G8B8:
		depth = 3;
		break;
	case D3DFMT_A8R8G8B8:
	case D3DFMT_X8R8G8B8:
		depth = 4;
		break;
	default:
		throw kError(_T("Unexpected image format!"));
	}

	const float scale = 1.0f/255.0f;
	const float invscale = 1.0f/scale;
	if (getOptions() & keImage_Opaque) {
		for (uint32_t y = rect.top; y < (uint32_t)rect.bottom; y++)
			for (uint32_t x = rect.left; x < (uint32_t)rect.right; x++) {
				uint8_t* ptr = (uint8_t*)rc.pBits + (y-rect.top)*rc.Pitch + (x-rect.left)*depth;

				kColor color(ptr[2]*scale, ptr[1]*scale, ptr[0]*scale);
				lambda(kVec2i(x, y), color);
				
				if (!readonly) {
					ptr[2] = (uint8_t)(color.r*invscale + 0.5f);
					ptr[1] = (uint8_t)(color.g*invscale + 0.5f);
					ptr[0] = (uint8_t)(color.b*invscale + 0.5f);
				}
			}
	} else {
		for (uint32_t y = rect.top; y < (uint32_t)rect.bottom; y++)
			for (uint32_t x = rect.left; x < (uint32_t)rect.right; x++) {
				uint8_t* ptr = (uint8_t*)rc.pBits + (y-rect.top)*rc.Pitch + (x-rect.left)*depth;

				kColor color(ptr[3]*scale, ptr[2]*scale, ptr[1]*scale, ptr[0]*scale);
				lambda(kVec2i(x, y), color);

				if (!readonly) {
					ptr[3] = (uint8_t)(color.a*invscale + 0.5f);
					ptr[2] = (uint8_t)(color.r*invscale + 0.5f);
					ptr[1] = (uint8_t)(color.g*invscale + 0.5f);
					ptr[0] = (uint8_t)(color.b*invscale + 0.5f);
				}
			}
	}

	if (_texture) {
		_texture->UnlockRect(0);
	} else {
		_surface->UnlockRect();
	}
}
